#!/usr/bin/env python
# encoding: utf-8

"""
@file: jiebudengshi.py
@time: 2016/11/21 上午10:03
"""
# 所有求解不等式的问题
from mathsolver.functions.base import *
from mathsolver.functions.budengshi import common_opers as co
from sympy import S, expand, fraction, simplify, cancel, degree_list, Abs, pi, solveset
from mathsolver.functions.budengshi.ineqs_funcs import AbsIneq, ChainInEq, JieBuDengShi004
from mathsolver.functions.budengshi.fenduanbudengshi import FenDuanBuDengShi
from mathsolver.functions.budengshi.hancanbudengshi import HanCanBuDengShi
from sympy.sets.conditionset import ConditionSet
from mathsolver.functions.mathematica.mathematicaSolve import MathematicaSolve
from mathsolver.functions.budengshi.params_ineqs_funcs import BaseParamerInterval


def safe_degree(f, symbol):
    try:
        d = degree_list(f, sympify(str(symbol)))[0]
    except Exception:
        mo, de = fraction(cancel(d))


# 使用sympy自带的解不等式
# 已知函数f(x)=|x+1|-|2x-3|,求不等式|f(x)|>1的解集
# Input1 paramer1: ineq
# Input2 paramer1: f表达式, paramer2: ineq
class JieBuDengShi001(BaseFunction):
    def solver(self, *args):
        self.label.add('解不等式')
        if len(args) == 1 and isinstance(args[0], BaseIneq):
            ineq = args[0]
            left, o, right = ineq.sympify()
            f = left - right
            variable = None
            if f.free_symbols:
                variable = list(f.free_symbols)[0]
            if len(f.free_symbols) > 1:
                raise Exception('try error')
            ineq_set = solveset(str(f) + str(o) + '0', domain=S.Reals)
            if isinstance(ineq_set, ConditionSet):
                raise Exception('Beyond Sympy calculate')
            if variable:
                self.steps.append(['解得:', new_latex(ineq_set)])
                self.output.append(BaseSymbolValue({variable: ineq_set}))
            else:
                self.steps.append(['解得:', new_latex(ineq_set)])
                self.output.append(BaseSymbolValue({None: ineq_set}))
        elif len(args) == 1 and isinstance(args[0], BaseIneqs):
            ineqs = args[0].sympify()
            # 过滤掉左右边含有oo ['-oo', '<', -2, '<', 'oo']
            ineqs = list(filter(lambda _: not (str(_[0]).find('oo') >= 0 or str(_[-1]).find('oo') >= 0), ineqs))
            if not ineqs:
                self.steps.append(['解得:', new_latex(S.Reals)])
                self.output.append(BaseSymbolValue({None: S.Reals}))
            else:
                intls = map(lambda tmp_ineq: solveset(''.join(map(str, tmp_ineq)), domain=S.Reals), ineqs)
                # last_intl = reduce(co.intervals_intersect, intls)
                last_intl = co.intervals_intersect(intls)
                # first_ineq = ineqs[0]
                # left, o, right = first_ineq.sympify()
                # f = left - right
                # variables = list(f.free_symbols)
                variables = co.ineqs_symbs(ineqs)
                if len(variables) == 0:
                    if isinstance(last_intl, ConditionSet):
                        raise Exception('Beyond Sympy calculate')
                    self.steps.append(['解得:', new_latex(last_intl)])
                    self.output.append(BaseSymbolValue({None: last_intl}))
                else:
                    variable = variables[0]
                    if len(variables) > 1:
                        raise Exception('try error')
                    if isinstance(last_intl, ConditionSet):
                        raise Exception('Beyond Sympy calculate')
                    self.steps.append(['解得:', new_latex(last_intl)])
                    self.output.append(BaseSymbolValue({variable: last_intl}))
        elif len(args) == 2:
            f = args[0].expression
            variable = list(f.free_symbols)[0]
            ineq = args[1]
            l_expr, ineq_op, r_expr = ineq.sympify()
            if isinstance(l_expr, Abs):
                ineq1 = BaseIneq([l_expr.args[0], ineq_op, r_expr])
                ineq2 = BaseIneq([l_expr.args[0], co.reverse_ineq_op(ineq_op), -r_expr])
                self.steps.append(['由题意可知 ' + ineq1.printing(), '或' + ineq2.printing()])
                l1, o1, r1 = ineq1.sympify()
                l2, o2, r2 = ineq2.sympify()
                f1 = l1 - r1
                f2 = l2 - r2
                ineq1_solve = solveset(str(f1) + str(o1) + '0', domain=S.Reals)
                ineq2_solve = solveset(str(f2) + str(o2) + '0', domain=S.Reals)
                if isinstance(ineq1_solve, ConditionSet) or isinstance(ineq2_solve, ConditionSet):
                    raise Exception('Beyond Sympy calculate')
                self.steps.append(['解的:' + new_latex(ineq1_solve), '或' + new_latex(ineq2_solve)])
                ineq_solve = ineq1_solve.union(ineq2_solve)
                self.steps.append(['故答案为: ' + new_latex(ineq_solve), ''])
                self.output.append(BaseSymbolValue({variable: ineq_solve}))
            else:
                f = l_expr - r_expr
                variable = list(f.free_symbols)[0]
                if len(f.free_symbols) > 1:
                    raise Exception('try error')
                ineq_set = solveset(str(f) + str(ineq_op) + '0', domain=S.Reals)
                if isinstance(ineq_set, ConditionSet):
                    raise Exception('Beyond Sympy calculate')
                self.steps.append(['解得:', new_latex(ineq_set)])
                self.output.append(BaseSymbolValue({variable: ineq_set}))
        else:
            raise Exception('Type Match Error')
        return self


# 不等式组
# input param1:BaseIneqs
class JieBuDengShi002(BaseFunction):
    def solver(self, *args):
        ineqs = args[0]
        if not isinstance(ineqs, BaseIneqs):
            raise Exception('Type Match Error')
        ineqs_list = ineqs.sympify()
        intls = []
        x_symbol = co.ineqs_symbs(ineqs.sympify())[0]
        for tmp_ineq in ineqs_list:
            if len(tmp_ineq) == 3:
                intls.append(solveset(''.join(map(str, tmp_ineq)), domain=S.Reals))
            elif len(tmp_ineq) == 5:
                tmp_solveset = ChainInEq(verbose=True).solver(''.join(map(str, tmp_ineq))).output[0]
                intls.append(tmp_solveset)
            else:
                raise Exception('not a available ineq')
        last_solveset = co.intervals_intersect(intls)
        self.output.append(BaseSymbolValue({x_symbol: last_solveset}))
        return self


class JieBuDengShi003(BaseFunction):
    def solver(self, *args):
        self.label.add('已知含参不等式的解集求不等式')
        ineq = args[0]
        x_symbol = sympify('x')
        l_expr, op, r_expr = ineq.sympify()
        ineq_f = l_expr - r_expr
        param_symbs = list(FiniteSet(*map(sympify, sorted(map(str, list(ineq_f.free_symbols))))) - FiniteSet(x_symbol))
        param_values = self.searchs(param_symbs)
        if not param_values:
            raise Exception('Type Match Error')
        param_values = map(lambda pv: (pv[0], list(pv[1])[0]), param_values.items())
        param_domains = {}
        for param_symb, param_value in param_values:
            tmp_intl = self.search(str(param_symb) + '_domain')
            if tmp_intl:
                param_domains[param_symb] = tmp_intl
                other_symb = list(param_value.free_symbols)[0]
                intl_ineq = co.interval_2_ineq_list(tmp_intl, param_value)[0]
                other_intl = solveset(''.join(map(str, intl_ineq)), domain=S.Reals)
                param_domains[other_symb] = other_intl
        param_eqs = BaseEqs(param_values)
        self.steps.append(['由题意可知', param_eqs.printing()])
        ineq_f = ineq_f.subs(param_eqs.sympify())
        mo, de = fraction(cancel(ineq_f))
        if not de.is_real:
            ineq_f = expand(mo * de)
        last_param_symb = list(FiniteSet(*ineq_f.free_symbols) - FiniteSet(x_symbol))[0]
        x_high_degree = degree_list(ineq_f, x_symbol)[0]
        x_hight_coef = ineq_f.coeff(x_symbol ** x_high_degree)
        if x_hight_coef in param_domains:
            tmp_intl = param_domains[x_hight_coef]
            if tmp_intl.is_subset(Interval(0, S.Infinity)):
                ineq_f = simplify(ineq_f / x_hight_coef)
            elif tmp_intl.is_subset(Interval(-S.Infinity, 0)):
                ineq_f = simplify(ineq_f / x_hight_coef)
                op = co.reverse_ineq_op(op)
        new_ineq = BaseIneq([ineq_f, op, '0'])
        self.steps.append(['原不等式等价于:', new_ineq.printing()])
        self.output.append(new_ineq)
        return self


# 解指数不等式 a**f(x) < b**g(x)
class JieBuDengShi004(BaseFunction):
    @staticmethod
    def stand_expon_ineq(ineq):
        l_expr, ineq_op, r_expr = ineq.sympify()
        f = l_expr - r_expr
        mons, const = co.symoms_and_const()
        if not (const == 0 and len(mons) == 0):
            raise Exception("Type Match Error")
        mon1, mon2 = mons
        new_l_expr = mon1 if mon1.as_coefficients_dict()[0][1] > 0 else mon2
        new_r_expr = -(f - new_l_expr)
        return BaseIneq([new_l_expr, ineq_op, new_r_expr])

    def solver(self, *args):
        ineq = args[0]
        ineq = JieBuDengShi004.stand_expon_ineq(ineq)
        l_expr, ineqp_op, r_expr = ineq.sympify()
        solve_set = None
        try:
            solve_set = solveset(str(l_expr) + ineqp_op + str(r_expr), domain=S.Reals)
        except Exception:
            pass
        if not solve_set or isinstance(solve_set, ConditionSet):  # sympy自带的无法解决的
            base_num1, expon_num1 = l_expr.args
            base_num2, expon_num2 = r_expr.args
            if base_num1 != base_num2:
                pass
            else:
                pass
        return self


class JieBuDengShi005(BaseFunction):
    def solver(self, *args):
        self.label.add('解不等式')
        if len(args) == 2:
            arg0 = args[0]
            arg1 = args[1]
            if isinstance(arg0, BaseFunc):
                f = arg0.expression
        raise Exception("Not Solve")


# 用于测试
class JieBuDengShiTest(BaseFunction):
    def solver(self, *args):
        return self


# 绝对值不等式 主要是为了输出步骤
# Input paramer1: 绝对值不等式
class JueDuiZhiBuDengShi(BaseFunction):
    def solver(self, *args):
        self.label.add('解绝对值不等式')
        abs_ineq = args[0]
        l, op, r = abs_ineq.sympify()
        f = l - r
        if not co.is_abs_expr(f) or len(f.free_symbols) > 1:  # 不含参的绝对值不等式
            raise Exception('Type Match Error')
        var_symbol = list(f.free_symbols)[0]
        from mathsolver.functions.hanshu.qujueduizhi import QuJueDuiZhi
        _solver1 = QuJueDuiZhi(known=self.known).solver(BaseEq(['f(%s)' % str(var_symbol), f]))
        self.steps.extend(_solver1.steps)
        output1 = _solver1.output
        f2 = BasePieceFunc({'var': var_symbol, 'type': '', 'name': 'f', 'expression': output1})
        from mathsolver.functions.hanshu.fenduan_jiebudengshi import FenDuanHanShuJieBuDengShi
        _solver2 = FenDuanHanShuJieBuDengShi(known=self.known).solver(f2, BaseIneq(['f(%s)' % str(var_symbol), op, '0']))
        self.steps.extend(_solver2.steps)
        self.output.extend(_solver2.output)
        return self


# MatcheMatica接不等式
class MatheMatica(BaseFunction):
    @staticmethod
    def _sub_pi(intl):
        if isinstance(intl, Interval):
            left = intl.left
            right = intl.right
            if left != -S.Infinity:
                left = sympify(left).subs('Pi', pi)
            if right != S.Infinity:
                right = sympify(right).subs('Pi', pi)
            return Interval(left, right, intl.left_open, intl.right_open)
        return intl

    def solver(self, *args):
        self.label.add('解不等式')
        ineq_var = sympify('x')  # 默认为x
        assign_var = None  # 指定的变量
        arg0 = args[0]  # 第一个参数 必须为 不等式或者不等式组
        for arg in args:
            if isinstance(arg, BaseVariable):
                assign_var = arg.sympify()
            elif isinstance(arg, BaseVariables):
                _symbs = arg.sympify()
                if len(_symbs) == 1:
                    assign_var = _symbs[0]
        if len(args) == 2 and isinstance(args[1], BaseVariable):  # 指定变量
            ineq_var = args[1].sympify()
        elif len(args) == 2 and isinstance(args[0], BaseFunc) and isinstance(args[1], BaseIneq):
            _, ineq_expr = args[0].sympify()
            arg0 = args[1]
        elif len(args) == 1 and isinstance(args[0], (BaseIneq, BaseIneqs)):
            _ineqs = []
            if isinstance(args[0], BaseIneq):
                _ineqs.append(args[0].sympify())
            else:
                _ineqs.extend(args[0].sympify())
            _symbs = co.ineqs_symbs(_ineqs)
            if len(_symbs) == 1:
                ineq_var = _symbs[0]
        if isinstance(arg0, BaseIneq):
            fl, op, fr = arg0.sympify()
            f = fl - fr
            symbs = list(f.free_symbols)
        elif isinstance(arg0, BaseIneqs):
            symbs = co.ineqs_symbs(arg0.sympify())
            ineqs = list(filter(lambda _: not (str(_[0]).find('oo') >= 0 or str(_[-1]).find('oo') >= 0), arg0.sympify()))
            arg0 = BaseIneqs(ineqs)
        else:
            raise Exception('Ineq Can Not Solve')
        if ineq_var:
            symbs.append(sympify(ineq_var))
            symbs = list(map(sympify, set(symbs)))
        symbs = list(map(sympify, set(symbs)))
        if len(symbs) == 0:
            raise Exception('Type Match Error')
        if len(symbs) == 1:
            ineq_var = symbs[0]
        if assign_var:  # 如果有指定变量 那么就用指定的变量
            ineq_var = assign_var
        # try:
        if isinstance(args[-1], dict):
            mr = MathematicaSolve().solver(arg0, BaseVariables([ineq_var]), args[-1]).output[0]
        else:
            natural_symb = None  # 自然数变量
            for _sym in symbs:
                if self.search(_sym) and self.search(_sym) == S.Integers:
                    natural_symb = _sym
            if natural_symb:
                mr = MathematicaSolve().solver(arg0, BaseVariables([ineq_var]),
                                               BaseBelong([str(natural_symb), '\\in', 'Z'])).output[0]
            else:
                mr = MathematicaSolve().solver(arg0, BaseVariables([ineq_var])).output[0]
        if isinstance(mr, BaseVariable):
            _flag = mr.sympify()
            if _flag:
                self.steps.append(['解得' + new_latex(assign_var), '解集为:' + new_latex(S.Reals)])
                self.output.append(BaseSymbolValue({assign_var: S.Reals}))
            else:
                self.steps.append(['解得' + new_latex(assign_var), '解集为:' + new_latex(EmptySet())])
                self.output.append(BaseSymbolValue({assign_var: EmptySet()}))
            return self
        if len(symbs) == 1:
            symb = list(symbs)[0]
            intl_iterms = map(lambda _: (_[0], MatheMatica._sub_pi(_[1])), mr.sympify().items())
            # intl = dict(intl_iterms)
            intls = []
            for _, tmp_intl in intl_iterms:
                intls.append(tmp_intl)
            if not intls:  # 空集
                self.steps.append(['解得' + new_latex(assign_var), '解集为:' + new_latex(EmptySet())])
                self.output.append(BaseSymbolValue({assign_var: EmptySet()}))
                return self
            last_intl = co.intervals_union(intls)
            self.steps.append(['解得' + new_latex(symbs[0]) + '的解集为', new_latex(last_intl)])
            self.output.append(BaseSymbolValue({symb: last_intl}))
            return self
        elif len(symbs) == 2:
            # x_symbol = sympify('x')
            ineq_symbol = ineq_var
            other_symbol = list(FiniteSet(*symbs) - FiniteSet(ineq_symbol))[0]
            intervals = []
            segs = mr.sympify()
            for symbs, intl_segs in segs.items():
                symbs = symbs
                if co.iterable(symbs):
                    if len(symbs) == 2:
                        seg_intl_list = list(intl_segs)
                        for intl1, intl2 in seg_intl_list:
                            if str(symbs[0]) == str(ineq_var):
                                intervals.append((intl2, intl1))
                            else:
                                intervals.append((intl1, intl2))
                    elif len(symbs) == 2:
                        symb = symbs[0]
                        seg_intl_list = list(intl_segs)
                        for intl in seg_intl_list:
                            if str(symb) == str(ineq_var):
                                intervals.append((S.Reals, intl))
                            else:
                                intervals.append((intl, S.Reals))
                else:
                    symb = symbs
                    if isinstance(intl_segs, FiniteSet):
                        seg_intl_list = list(intl_segs)
                        for intl in seg_intl_list:
                            if str(symb) == str(ineq_var):
                                intervals.append((S.Reals, intl))
                            else:
                                intervals.append((intl, S.Reals))
                    elif isinstance(intl_segs, Interval):
                        if symb == ineq_var:
                            intervals.append((S.Reals, intl_segs))
                        elif symb == other_symbol:
                            intervals.append((intl_segs, S.Reals))
            if len(intervals) == 1 and intervals[0][0] == S.Reals:
                new_intl = MatheMatica._sub_pi(intervals[0][1])
                self.steps.append(['解得不等式解集为:', new_latex(new_intl)])
                self.output.append(BaseSymbolValue({ineq_var: new_intl}))
                return self
            intervals_map = {}
            for param_intl, x_intl in intervals:
                param_intl = MatheMatica._sub_pi(param_intl)
                x_intl = MatheMatica._sub_pi(x_intl)
                tmp_value = intervals_map.get(param_intl)
                if not tmp_value:
                    intervals_map[param_intl] = x_intl
                else:
                    intervals_map[param_intl] = tmp_value.union(x_intl)
            intervals = intervals_map.items()
            parm_result = BaseParamerInterval(
                {'var': str(ineq_var), 'paramer_var': str(other_symbol), 'solveset': intervals})
            for parm_intl, sym_intl in intervals:
                self.steps.append(
                    ['当' + new_latex(other_symbol) + ' \\in ' + new_latex(parm_intl) + '时',
                     new_latex(ineq_var) + ' \\in ' + new_latex(sym_intl)])
            self.output.append(parm_result)
            return self
        else:
            raise Exception('Input the parameters of the qualified')


class HengChengLiBuDengShi(BaseFunction):
    def solver(self, *args):
        self.label.add('解不等式')

        return self


# 解不等式的入口(含参和不含参)
# input: 第一个不等式(必填);
class JieBuDengShi(BaseFunction):
    CLS = [JueDuiZhiBuDengShi, MatheMatica, JieBuDengShi001, JieBuDengShi003, AbsIneq, ChainInEq, JieBuDengShi004,
           JieBuDengShi002,
           FenDuanBuDengShi, HanCanBuDengShi]

    @staticmethod
    def _is_trig_ineq(_args):
        flag = False
        args_str = ''
        for _arg in _args:
            if isinstance(_arg, dict):
                pass
            else:
                arg_str = str(_arg.sympify())
                args_str += arg_str
                if args_str.find('sin') >= 0 or args_str.find('cos') >= 0 \
                        or args_str.find('tan') >= 0 or args_str.find('cot') >= 0:
                    flag = True
        return flag

    def solver(self, *args):
        tmp_args = [arg for arg in args]
        # if 'f' in self.known: # 2017-05-09
        #     tmp_args.insert(0, self.known['f'])
        solve_r = None
        if JieBuDengShi._is_trig_ineq(tmp_args):
            from mathsolver.functions.sanjiao.sanjiaobudengshi import JieSanJiaoBuDengShi
            return JieSanJiaoBuDengShi(verbose=True).solver(*tmp_args)
        assign_var = None
        for arg in args:
            if isinstance(arg, BaseVariable):
                assign_var = arg.sympify()
            elif isinstance(arg, BaseVariables):
                assign_var = arg.sympify()[0]
        for cl in JieBuDengShi.CLS:
            try:
                solve_r = cl(verbose=True)
                solve_r.known = self.known
                solve_r = solve_r.solver(*tmp_args)
                # --------Ouput------处理
                _output = solve_r.output
                if assign_var:
                    for i, _o in enumerate(_output):
                        if isinstance(_o, BaseSymbolValue):
                            _d = _o.sympify()
                            for _k, _v in _d.items():
                                if not _k:
                                    _d[assign_var] = _v
                solve_r.output = _output
                break
            except Exception:
                solve_r = None
        if not solve_r:
            raise Exception('Can\'t solve Ineq')
        return solve_r


if __name__ == '__main__':
    pass
